export function isString <T> (value: T): boolean {
  return typeof value === 'string'
}

export function isNumber <T> (value: T): boolean {
  return typeof value === 'number'
}

export function isArray <T> (value: T): boolean {
  return Object.prototype.toString.call(value) === '[object Array]'
}

export function isObject <T> (value: T): boolean {
  return Object.prototype.toString.call(value) === '[object Object]'
}

export function isBlank <T> (value: T): boolean {
  if (value === null || value === undefined) {
    return true
  }
  if (typeof value === 'string' && value.trim().length === 0) {
    return true
  }
  if (Array.isArray(value) && value.length === 0) {
    return true
  }
  if (typeof value === 'object' && Object.keys(value).length === 0) {
    return true
  }
  return false
}

export function debounce<T extends (...args: any[]) => any>(
  func: T,
  delay: number
): (...args: Parameters<T>) => void {
  let timer: ReturnType<typeof setTimeout> | null = null

  return function debounced (...args: Parameters<T>): void {
    if (timer !== null) {
      clearTimeout(timer)
    }

    timer = setTimeout(() => {
      func(...args)
      timer = null
    }, delay)
  }
}

export function throttle<T extends (...args: any[]) => any>(
  func: T,
  delay: number
): (...args: Parameters<T>) => void {
  let lastTime = 0

  return function throttled (...args: Parameters<T>): void {
    const now = Date.now()

    if (now - lastTime >= delay) {
      func(...args)
      lastTime = now
    }
  }
}

export function deepClone<T> (obj: T): T {
  if (obj === null || typeof obj !== 'object') {
    return obj
  }

  const copy = Array.isArray(obj) ? [] : {} as any

  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      copy[key] = deepClone(obj[key])
    }
  }

  return copy as T
}
